package mysql

import (
	"errors"

	"go.uber.org/zap"

	"mxshop_srvs/inventory_srv/dao/redis"
	"mxshop_srvs/inventory_srv/model"
	proto "mxshop_srvs/inventory_srv/proto/gen/v1"
)

var ErrLowStocks = errors.New("insufficient stocks")

func (g *Gorm) GetInventory(goodsID int32) (i model.Inventory, err error) {
	err = g.db.Where(&model.Inventory{Goods: goodsID}).First(&i).Error
	return
}

func (g *Gorm) SetInventory(i model.Inventory, invNum int32) error {
	i.Stocks = invNum
	return g.db.Save(&i).Error
}

type InventoryUpdater interface {
	update([]*proto.GoodsInvInfo) error
}

type InventoryUpdate struct {
	updater InventoryUpdater
}

// SetUpdatePolicy 设置更新策略
func (inventory *InventoryUpdate) SetUpdatePolicy(inventoryUpdate InventoryUpdater) {
	inventory.updater = inventoryUpdate
}

// Update 调用策略中的方法
func (inventory *InventoryUpdate) Update(goodsInfos []*proto.GoodsInvInfo) error {
	return inventory.updater.update(goodsInfos)
}

type Sell struct {
	g *Gorm
	r *redis.Redis
}

func NewSell(g *Gorm, r *redis.Redis) *Sell {
	return &Sell{
		g: g,
		r: r,
	}
}

// Sell inventory with pessimistic lock.
// func (s *Sell) update(goodsInvInfos []*proto.GoodsInvInfo) error {
// 	tx := s.g.db.Begin()
// 	defer func() {
// 		if r := recover(); r != nil {
// 			tx.Rollback()
// 		}
// 	}()
//
// 	if err := tx.Error; err != nil {
// 		return err
// 	}
//
// 	for _, goodsInvInfo := range goodsInvInfos {
// 		// 查询商品
// 		var i model.Inventory
// 		err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where(&model.Inventory{Goods: goodsInvInfo.GoodsId}).First(&i).Error
// 		if err != nil {
// 			tx.Rollback()
// 			return err
// 		}
// 		// 判断库存是否充足
// 		if i.Stocks < goodsInvInfo.Num {
// 			tx.Rollback()
// 			return ErrLowStocks
// 		}
// 		// 扣减库存
// 		i.Stocks -= goodsInvInfo.Num
// 		err = tx.Save(&i).Error
// 		if err != nil {
// 			return err
// 		}
// 	}
//
// 	return tx.Commit().Error
// }

// Sell inventory with optimistic lock
func (s *Sell) update(goodsInvInfos []*proto.GoodsInvInfo) error {
	tx := s.g.db.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	if err := tx.Error; err != nil {
		return err
	}

	for _, goodsInvInfo := range goodsInvInfos {
		// 查询商品
		var i model.Inventory
		for {
			err := s.g.db.Where(&model.Inventory{Goods: goodsInvInfo.GoodsId}).First(&i).Error // 注意:查询的时候不可以用tx
			if err != nil {
				tx.Rollback()
				return err
			}
			// 判断库存是否充足
			if i.Stocks < goodsInvInfo.Num {
				tx.Rollback()
				return ErrLowStocks
			}
			// 扣减库存
			i.Stocks -= goodsInvInfo.Num
			oVersion := i.Version
			i.Version += 1
			// where条件需要特别注意,必须要查询旧的版本号.
			// 更新的时候需要注意:扣减库存的时候会出现stocks=0的情况,对于零值的更新需要用Select().
			row := tx.Model(&model.Inventory{}).Where(&model.Inventory{
				Goods:   goodsInvInfo.GoodsId,
				Version: oVersion,
			}, "Goods", "Version").Select("*").Omit("CreatedAt").Updates(i).RowsAffected
			if row == 0 {
				zap.L().Info("sell inventory failed", zap.Int32("goods_id", goodsInvInfo.GoodsId))
			} else {
				break
			}
		}
	}

	return tx.Commit().Error
}

// func (s *Sell) do(goodsInvInfo *proto.GoodsInvInfo, tx *gorm.DB, rs *redsync.Redsync) error {
// 	// 获取锁
// 	mutexName := fmt.Sprintf("goods_%d", goodsInvInfo.GoodsId)
// 	mutex := rs.NewMutex(mutexName)
// 	if err := mutex.Lock(); err != nil {
// 		zap.L().Error("lock failed", zap.String("mutexName", mutexName))
// 		return err
// 	}
// 	defer func(mutex *redsync.Mutex) {
// 		_, _ = mutex.Unlock()
// 	}(mutex)
//
// 	// 查询商品
// 	i, err := s.g.GetInventory(goodsInvInfo.GoodsId)
// 	if err != nil {
// 		tx.Rollback()
// 		return err
// 	}
// 	// 判断库存是否充足
// 	if i.Stocks < goodsInvInfo.Num {
// 		tx.Rollback()
// 		return ErrLowStocks
// 	}
// 	// 扣减库存
// 	// i.Stocks -= goodsInvInfo.Num
// 	// tx.Save(&i) // 此方式会出现扣减异常
// 	err = tx.Model(&i).Update("stocks", gorm.Expr("stocks - ?", goodsInvInfo.Num)).Error
// 	if err != nil {
// 		return err
// 	}
// 	return nil
// }
//
// // Sell inventory with redsync lock
// func (s *Sell) update(goodsInvInfos []*proto.GoodsInvInfo) error {
// 	tx := s.g.db.Begin()
// 	defer func() {
// 		if r := recover(); r != nil {
// 			tx.Rollback()
// 		}
// 	}()
//
// 	if err := tx.Error; err != nil {
// 		return err
// 	}
//
// 	var rs = s.r.NewRedsync()
// 	for _, goodsInvInfo := range goodsInvInfos {
// 		err := s.do(goodsInvInfo, tx, rs)
// 		if err != nil {
// 			tx.Rollback()
// 			return err
// 		}
// 		// // 获取锁
// 		// mutexName := fmt.Sprintf("goods_%d", goodsInvInfo.GoodsId)
// 		// mutex := rs.NewMutex(mutexName)
// 		// if err := mutex.Lock(); err != nil {
// 		// 	zap.L().Error("lock failed", zap.String("mutexName", mutexName))
// 		// 	return err
// 		// }
// 		//
// 		// // 查询商品
// 		// var i model.Inventory
// 		//
// 		// err := s.g.db.Where(&model.Inventory{Goods: goodsInvInfo.GoodsId}).First(&i).Error // 注意:查询的时候不可以用tx
// 		// if err != nil {
// 		// 	tx.Rollback()
// 		// 	// _, _ = mutex.Unlock()
// 		// 	return err
// 		// }
// 		// // 判断库存是否充足
// 		// if i.Stocks < goodsInvInfo.Num {
// 		// 	tx.Rollback()
// 		// 	// _, _ = mutex.Unlock()
// 		// 	return ErrLowStocks
// 		// }
// 		// // 扣减库存
// 		// i.Stocks -= goodsInvInfo.Num
// 		// tx.Save(&i)
// 		//
// 		// // 释放锁
// 		// if ok, err := mutex.Unlock(); !ok || err != nil {
// 		// 	return err
// 		// }
// 	}
// 	tx.Commit()
// 	return nil
// }

type ReBack struct {
	g *Gorm
}

func NewReBack(g *Gorm) *ReBack {
	return &ReBack{
		g: g,
	}
}

// ReBack inventory with pessimistic lock.
// func (r *ReBack) update(goodsInvInfos []*proto.GoodsInvInfo) error {
// 	tx := r.g.db.Begin()
// 	defer func() {
// 		if r := recover(); r != nil {
// 			tx.Rollback()
// 		}
// 	}()
//
// 	if err := tx.Error; err != nil {
// 		return err
// 	}
//
// 	for _, goodsInvInfo := range goodsInvInfos {
// 		// 查询商品
// 		var i model.Inventory
// 		err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where(&model.Inventory{Goods: goodsInvInfo.GoodsId}).First(&i).Error
// 		if err != nil {
// 			tx.Rollback()
// 			return err
// 		}
// 		// 归还库存
// 		i.Stocks += goodsInvInfo.Num
// 		err = tx.Save(&i).Error
// 		if err != nil {
// 			return err
// 		}
// 	}
//
// 	return tx.Commit().Error
// }

// ReBack inventory with optimistic lock.
func (r *ReBack) update(goodsInvInfos []*proto.GoodsInvInfo) error {
	tx := r.g.db.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	if err := tx.Error; err != nil {
		return err
	}

	for _, goodsInvInfo := range goodsInvInfos {
		// 查询商品
		var i model.Inventory
		for {
			err := r.g.db.Where(&model.Inventory{Goods: goodsInvInfo.GoodsId}).First(&i).Error // 注意:查询的时候不可以用tx
			if err != nil {
				tx.Rollback()
				return err
			}
			// 归还库存
			i.Stocks += goodsInvInfo.Num
			oVersion := i.Version
			i.Version -= 1
			// where条件需要特别注意,必须要查询旧的版本号.
			// 更新的时候需要注意:归还库存,减少版本号的时候会出现version=0的情况,对于零值的更新需要用Select().
			row := tx.Model(&model.Inventory{}).Where(&model.Inventory{
				Goods:   goodsInvInfo.GoodsId,
				Version: oVersion,
			}, "Goods", "Version").Select("*").Omit("CreatedAt").Updates(i).RowsAffected
			if row == 0 {
				zap.L().Info("return inventory failed", zap.Int32("goods_id", goodsInvInfo.GoodsId))
			} else {
				break
			}
		}
	}

	return tx.Commit().Error
}
